﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.Text;
using Arch.Common.IoC;
using Arch.IoC;
using Microsoft.Practices.ServiceLocation;
using Microsoft.Practices.Unity;
using System.ServiceModel.Configuration;

namespace Arch.Services.ErrorHandling
{
    public sealed class AutoFaultContractServiceBehavior : BehaviorExtensionElement, IServiceBehavior
    {
        [Dependency]
        public IEnumerable<IExceptionToFaultHandler> ExceptionToFaultHandlers { get; set; }

        public AutoFaultContractServiceBehavior()
        {
            ServiceLocator.Current.BuildUp(this); // TODO: Integrate ServiceBehaviors creation in unity
        }

        #region IServiceBehavior Members

        public void AddBindingParameters(ServiceDescription serviceDescription,
            ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints,
            BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyDispatchBehavior(ServiceDescription serviceDescription,
            ServiceHostBase serviceHostBase)
        {
            foreach (ServiceEndpoint ep in serviceDescription.Endpoints)
            {
                foreach (OperationDescription opDesc in ep.Contract.Operations)
                {
                    foreach (var faultType in ExceptionToFaultHandlers.SelectMany(h => h.HandledContracts))
                    {
                        Type type = faultType;
                        if (opDesc.Faults.Any(f => f.DetailType == type))
                            continue;

                        string msgAction = GetMessageAction(ep.Contract,
                                                            opDesc.Name + faultType.Name,
                                                            null,
                                                            false);

                        FaultDescription faultDescription = new FaultDescription(msgAction)
                        {
                            Name = faultType.Name,
                            Namespace = ep.Contract.Namespace,
                            DetailType = faultType
                        };

                        //HACK : ElementName must be null to behave like a FaultContractAttribute without Name 
                        typeof(FaultDescription).GetProperty("ElementName",
                                                             System.Reflection.BindingFlags.Instance
                                                             | System.Reflection.BindingFlags.NonPublic)
                            .SetValue(faultDescription, null, null);

                        opDesc.Faults.Add(faultDescription);
                    }
                }
            }
        }

        public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
        }

        #endregion

        #region adapted from System.ServiceModel.Description.TypeLoader
        internal static string GetMessageAction(ContractDescription contract, string opname, string action, bool isResponse)
        {
            if (action != null)
            {
                return action;
            }
            StringBuilder builder = new StringBuilder(0x40);
            if (string.IsNullOrEmpty(contract.Namespace))
            {
                builder.Append("urn:");
            }
            else
            {
                builder.Append(contract.Namespace);
                if (!contract.Namespace.EndsWith("/", StringComparison.Ordinal))
                {
                    builder.Append('/');
                }
            }
            builder.Append(contract.Name);
            builder.Append('/');
            action = isResponse ? (opname + "Response") : opname;
            return CombineUriStrings(builder.ToString(), action);
        }

        internal static string CombineUriStrings(string baseUri, string path)
        {
            if (Uri.IsWellFormedUriString(path, UriKind.Absolute) || (path == string.Empty))
            {
                return path;
            }
            if (baseUri.EndsWith("/", StringComparison.Ordinal))
            {
                return (baseUri + (path.StartsWith("/", StringComparison.Ordinal) ? path.Substring(1) : path));
            }
            return (baseUri + (path.StartsWith("/", StringComparison.Ordinal) ? path : ("/" + path)));
        }
        #endregion

        public override Type BehaviorType
        {
            get { return typeof(AutoFaultContractServiceBehavior); }
        }

        protected override object CreateBehavior()
        {
            return new AutoFaultContractServiceBehavior();
        }
    }
}
